/*  SSXMLParser.m
 *  MESLib
 * 
 * Copyright 2003-2009 Starline Software Ltd
 * 
 * This file is part of MESLib.
 * 
 * MESLib is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * MESLib is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with MESLib. If not, see <http://www.gnu.org/licenses/>.
 */
//  Created by Tobias Haughton on Tue Jan 21 2003.
//



//the SSXMLParser class reads data from _structure.xml and event files and builds data structures
//based on that.


#import "SSXMLParser.h"
#import "SSCatalystComment.h"

@implementation SSXMLParser

- (NSMutableDictionary *)dictionaryWithStructureXMLData:(NSData *)data fileName:fileName actorOrderArray:(NSMutableArray **)orderArray successful:(BOOL *)success
{
   //dictionaryWithStructureXMLData builds and returns a dictionary from the data contained in _structure.xml files
   //orderArray should be a shared array so that the order can be set for multiple nested .mes directories
   *success = YES;

   NSString *xmlString = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
   NSMutableDictionary *structureDictionary = [[NSMutableDictionary alloc] init];
   NSString *uniqueIDString = [self stringFromXMLString:xmlString openTag:@"<meschart unique_id=\"" closeTag:@"\">"];
   if([uniqueIDString isEqualToString:[NSString string]]) {
      //no unique id
      *success = NO;
   }

   NSString *titleString = [self stringFromXMLString:xmlString openTag:@"<title>" closeTag:@"</title>"];
   NSString *authorString = [self stringFromXMLString:xmlString openTag:@"<author>" closeTag:@"</author>"];
   NSCalendarDate *chartDate = [self chartDateFromXMLString:xmlString];
   NSString *projectFolder = [self stringFromXMLString:xmlString openTag:@"<projectFolder>" closeTag:@"</projectFolder>"];
   NSString *whiteString = [self stringFromXMLString:xmlString openTag:@"<white_color_designation>" closeTag:@"</white_color_designation>"];
   NSString *yellowString = [self stringFromXMLString:xmlString openTag:@"<yellow_color_designation>" closeTag:@"</yellow_color_designation>"];
   NSString *greenString = [self stringFromXMLString:xmlString openTag:@"<green_color_designation>" closeTag:@"</green_color_designation>"];
   NSString *lightBlueString = [self stringFromXMLString:xmlString openTag:@"<light_blue_color_designation>" closeTag:@"</light_blue_color_designation>"];
   NSString *blueString = [self stringFromXMLString:xmlString openTag:@"<blue_color_designation>" closeTag:@"</blue_color_designation>"];
   NSString *purpleString = [self stringFromXMLString:xmlString openTag:@"<purple_color_designation>" closeTag:@"</purple_color_designation>"];
   NSString *orangeString = [self stringFromXMLString:xmlString openTag:@"<orange_color_designation>" closeTag:@"</orange_color_designation>"];
   NSString *redString = [self stringFromXMLString:xmlString openTag:@"<red_color_designation>" closeTag:@"</red_color_designation>"];

   NSString *diamandUDValue1String = [self stringFromXMLString:xmlString openTag:@"<diamond_UDValue_1>" closeTag:@"</diamond_UDValue_1>"];
   NSString *diamandUDValue2String = [self stringFromXMLString:xmlString openTag:@"<diamond_UDValue_2>" closeTag:@"</diamond_UDValue_2>"];
   NSString *diamandUDValue3String = [self stringFromXMLString:xmlString openTag:@"<diamond_UDValue_3>" closeTag:@"</diamond_UDValue_3>"];
   NSString *diamandUDValue4String = [self stringFromXMLString:xmlString openTag:@"<diamond_UDValue_4>" closeTag:@"</diamond_UDValue_4>"];
   NSString *diamandUDValue5String = [self stringFromXMLString:xmlString openTag:@"<diamond_UDValue_5>" closeTag:@"</diamond_UDValue_5>"];
   NSString *diamandUDValue6String = [self stringFromXMLString:xmlString openTag:@"<diamond_UDValue_6>" closeTag:@"</diamond_UDValue_6>"];
   NSString *diamandOptionUDValue1String = [self stringFromXMLString:xmlString openTag:@"<diamond_option_UDValue_1>" closeTag:@"</diamond_option_UDValue_1>"];
   NSString *diamandOptionUDValue2String = [self stringFromXMLString:xmlString openTag:@"<diamond_option_UDValue_2>" closeTag:@"</diamond_option_UDValue_2>"];
   NSString *diamandOptionUDValue3String = [self stringFromXMLString:xmlString openTag:@"<diamond_option_UDValue_3>" closeTag:@"</diamond_option_UDValue_3>"];
   NSString *diamandOptionUDValue4String = [self stringFromXMLString:xmlString openTag:@"<diamond_option_UDValue_4>" closeTag:@"</diamond_option_UDValue_4>"];
   NSString *diamandOptionUDValue5String = [self stringFromXMLString:xmlString openTag:@"<diamond_option_UDValue_5>" closeTag:@"</diamond_option_UDValue_5>"];
   NSString *diamandTradeoffUDValue1String = [self stringFromXMLString:xmlString openTag:@"<diamond_tradeoff_UDValue_1>" closeTag:@"</diamond_tradeoff_UDValue_1>"];
   NSString *diamandTradeoffUDValue2String = [self stringFromXMLString:xmlString openTag:@"<diamond_tradeoff_UDValue_2>" closeTag:@"</diamond_tradeoff_UDValue_2>"];
   NSString *diamandTradeoffUDValue3String = [self stringFromXMLString:xmlString openTag:@"<diamond_tradeoff_UDValue_3>" closeTag:@"</diamond_tradeoff_UDValue_3>"];

   NSMutableArray *referencedEventBlockArray = [self referencedEventBlockArrayFromXMLString:xmlString];
   [self addActorOrderFromString:xmlString toArray:orderArray]; //add the actor order to the array
   NSMutableArray *commentArray = [self commentArrayFromXMLString:xmlString];

   NSMutableArray *poisonWordArray = [self poisonWordArrayFromXMLString:xmlString];
   
   [structureDictionary setObject:fileName forKey:@"fileName"];
   [structureDictionary setObject:uniqueIDString forKey:@"unique_id"];
   [structureDictionary setObject:titleString forKey:@"title"];
   [structureDictionary setObject:authorString forKey:@"author"];
   [structureDictionary setObject:chartDate forKey:@"chartDate"];
   [structureDictionary setObject:projectFolder forKey:@"projectFolder"];
   [structureDictionary setObject:whiteString forKey:@"whiteColorDesignation"];
   [structureDictionary setObject:yellowString forKey:@"yellowColorDesignation"];
   [structureDictionary setObject:greenString forKey:@"greenColorDesignation"];
   [structureDictionary setObject:lightBlueString forKey:@"lightBlueColorDesignation"];
   [structureDictionary setObject:blueString forKey:@"blueColorDesignation"];
   [structureDictionary setObject:purpleString forKey:@"purpleColorDesignation"];
   [structureDictionary setObject:orangeString forKey:@"orangeColorDesignation"];
   [structureDictionary setObject:redString forKey:@"redColorDesignation"];

   [structureDictionary setObject:diamandUDValue1String forKey:@"diamondUDValue1"];
   [structureDictionary setObject:diamandUDValue2String forKey:@"diamondUDValue2"];
   [structureDictionary setObject:diamandUDValue3String forKey:@"diamondUDValue3"];
   [structureDictionary setObject:diamandUDValue4String forKey:@"diamondUDValue4"];
   [structureDictionary setObject:diamandUDValue5String forKey:@"diamondUDValue5"];
   [structureDictionary setObject:diamandUDValue6String forKey:@"diamondUDValue6"];
   [structureDictionary setObject:diamandOptionUDValue1String forKey:@"diamondOptionUDValue1"];
   [structureDictionary setObject:diamandOptionUDValue2String forKey:@"diamondOptionUDValue2"];
   [structureDictionary setObject:diamandOptionUDValue3String forKey:@"diamondOptionUDValue3"];
   [structureDictionary setObject:diamandOptionUDValue4String forKey:@"diamondOptionUDValue4"];
   [structureDictionary setObject:diamandOptionUDValue5String forKey:@"diamondOptionUDValue5"];
   [structureDictionary setObject:diamandTradeoffUDValue1String forKey:@"diamondTradeoffUDValue1"];
   [structureDictionary setObject:diamandTradeoffUDValue2String forKey:@"diamondTradeoffUDValue2"];
   [structureDictionary setObject:diamandTradeoffUDValue3String forKey:@"diamondTradeoffUDValue3"];

   [structureDictionary setObject:referencedEventBlockArray forKey:@"referencedEventBlocks"];
   [structureDictionary setObject:commentArray forKey:@"comments"];
   [structureDictionary setObject:[NSMutableArray array] forKey:@"needsDiamonds"];
   [structureDictionary setObject:[NSMutableArray array] forKey:@"subDirectories"];

   [structureDictionary setObject:poisonWordArray forKey:@"poisonWords"];
   
   [structureDictionary autorelease];
   
   return structureDictionary;
}

- (SSEventObject *)eventObjectWithEventXMLData:(NSData *)data address:(NSString *)address label:(NSNumber *)label xPosition:(NSNumber *)xPosition successful:(BOOL *)success
{
   //eventObjectWithEventXMLData builds and returns an SSEventObject based on the data contained in an event xml file
   *success = YES;

   SSEventObject *event;
   NSString *xmlString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
   NSString *unique_id = [self stringFromXMLString:xmlString openTag:@"<mesblock unique_id=\"" closeTag:@"\">"];

   if([unique_id isEqualToString:[NSString string]]) {
      *success = NO;
   }

   NSString *actor = [self stringFromXMLString:xmlString openTag:@"<actor>" closeTag:@"</actor>"];
   NSString *action = [self stringFromXMLString:xmlString openTag:@"<action>" closeTag:@"</action>"];
   NSString *object = [self stringFromXMLString:xmlString openTag:@"<object>" closeTag:@"</object>"];
   NSString *location = [self stringFromXMLString:xmlString openTag:@"<location>" closeTag:@"</location>"];
   SSChartDate *beginTime = [self ssChartDateFromXMLString:xmlString openTag:@"<start_time type=\"" closeTag:@"</start_time>"];
   SSChartDate *endTime = [self ssChartDateFromXMLString:xmlString openTag:@"<end_time type=\"" closeTag:@"</end_time>"];
   NSArray *source = [[self stringFromXMLString:xmlString openTag:@"<source>" closeTag:@"</source>"] componentsSeparatedByString:@", "];
   NSString *remark = [self stringFromXMLString:xmlString openTag:@"<remarks>" closeTag:@"</remarks>"];
   NSMutableArray *links = [self linkArrayFromXMLString:xmlString];
   BOOL passedNSTest = [[self stringFromXMLString:xmlString openTag:@"<nstest>" closeTag:@"</nstest>"] isEqualToString:@"passed"];

   event = [[[SSEventObject alloc] initWithUnique_id:unique_id
                                               actor:actor
                                              action:action
                                              object:object
                                            location:location
                                           beginTime:beginTime
                                             endTime:endTime
                                              source:source
                                             remark:remark
                                             address:address
                                               label:label
                                        passedNSTest:passedNSTest
                                           xPosition:[xPosition floatValue]
                                               links:links] autorelease];

   return event;
}


- (NSMutableArray *)referencedEventBlockArrayFromXMLString:(NSString *)xmlString
{
   //returns an array with of all referenced event blocks

   BOOL getEvents = YES;
   NSRange rangeToSearch = NSMakeRange(0, [xmlString length]);
   NSRange openRange, closeRange;
   NSMutableArray *eventArray = [[[NSMutableArray alloc] init] autorelease];

   while(getEvents) {
      openRange = [xmlString rangeOfString:@"<block unique_id=\"" options:NSCaseInsensitiveSearch range:rangeToSearch];
      closeRange = [xmlString rangeOfString:@"</block>" options:NSCaseInsensitiveSearch range:rangeToSearch];

      if(openRange.location != NSNotFound && closeRange.location != NSNotFound) {
         //there is another event to add
         NSMutableDictionary *dictionary = [self referencedEventDictionaryFromString:[xmlString substringWithRange:NSMakeRange(openRange.location,
                                                                                                                    ((closeRange.location + closeRange.length) - openRange.location))]];
         if(dictionary) { //if not dictionary there was a problem with the string we passed to eventDictionaryFromString: so we dont want to add it
            [eventArray addObject:dictionary];
         }
         rangeToSearch = NSMakeRange((closeRange.location + closeRange.length),
                                     ([xmlString length] - (closeRange.location + closeRange.length))); //move the start of rangeToSearch to the end of last </block> tag
      } else {
         //no more valid event blocks
         getEvents = NO;
      }
   }
   return eventArray;
}

- (NSMutableDictionary *)referencedEventDictionaryFromString:(NSString *)eventString
{
   //builds a dictionary from the first event block contained in eventString
   NSMutableDictionary *eventDictionary;
   NSRange openRange = [eventString rangeOfString:@"<block unique_id=\"" options:NSCaseInsensitiveSearch];
   NSRange closeRange = [eventString rangeOfString:@"</block>" options:NSCaseInsensitiveSearch];
   NSString *address;
   NSNumber *label;
   NSNumber *xPos;

   if(openRange.location != NSNotFound && closeRange.location != NSNotFound) { //this test is just here in case the method is called without checking if there is an open and close tag
                                                                               //we should be dealing with a valid event block
                                                                               //get the address
      address = [self stringFromXMLString:eventString openTag:@"<address>" closeTag:@"</address>"];
      label = [self labelFromString:eventString];
      xPos = [self xPositionFromString:eventString];

      eventDictionary = [[[NSMutableDictionary alloc] init] autorelease];
      [eventDictionary setObject:address forKey:@"address"];
      [eventDictionary setObject:label forKey:@"label"];
      [eventDictionary setObject:xPos forKey:@"xPosition"];
      return eventDictionary;
   }
   return nil; //there was a problem making the dictionary
}

- (void)addActorOrderFromString:(NSString *)xmlString toArray:(NSMutableArray **)orderArray
{
   //pulls the order of actor rows out of the xmlString and adds dictionaries for each to the shared order array
   BOOL getOrder = YES;
   NSRange rangeToSearch = NSMakeRange(0, [xmlString length]);
   NSRange openRange, closeRange;

   while(getOrder) {
      openRange = [xmlString rangeOfString:@"<actor_row" options:NSCaseInsensitiveSearch range:rangeToSearch];
      closeRange = [xmlString rangeOfString:@"</actor>" options:NSCaseInsensitiveSearch range:rangeToSearch];

      if(openRange.location != NSNotFound && closeRange.location != NSNotFound) {
         //there is another actor row to add
         NSMutableDictionary *dictionary = [self actorOrderDictionaryFromString:[xmlString substringWithRange:NSMakeRange(openRange.location,
                                                                                                                          ((closeRange.location + closeRange.length) - openRange.location))]];
         if(dictionary) { //if not dictionary there was a problem with the string we passed to actorOrderDictionaryFromString: so we dont want to add it
            [*orderArray addObject:dictionary];
         }
         rangeToSearch = NSMakeRange((closeRange.location + closeRange.length),
                                     ([xmlString length] - (closeRange.location + closeRange.length))); //move the start of rangeToSearch to the end of last </block> tag
      } else {
         //no more valid actor rows
         getOrder = NO;
      }
   }

   //order the array to be sure
   [*orderArray sortUsingFunction:sortActorOrderArray context:nil];
}

- (NSMutableDictionary *)actorOrderDictionaryFromString:(NSString *)orderString
{
   //builds a dictionary from the first actor order block contained in order string
   NSMutableDictionary *orderDictionary;
   NSRange openRange = [orderString rangeOfString:@"<actor_row" options:NSCaseInsensitiveSearch];
   NSRange closeRange = [orderString rangeOfString:@"</actor>" options:NSCaseInsensitiveSearch];
   NSString *name;
   NSString *rowString;

   if(openRange.location != NSNotFound && closeRange.location != NSNotFound) { //this test is just here in case the method is called without checking if there is an open and close tag
      rowString = [self stringFromXMLString:orderString openTag:@"<actor_row " closeTag:@">"];
      name = [self stringFromXMLString:orderString openTag:@">" closeTag:@"</actor>"];

      orderDictionary = [[[NSMutableDictionary alloc] init] autorelease];
      [orderDictionary setObject:[NSNumber numberWithInt:[rowString intValue]] forKey:@"row"];
      [orderDictionary setObject:name forKey:@"name"];

      return orderDictionary;
   }
   return nil; //there was a problem making the dictionary
}

- (NSMutableArray *)diamondArrayFromXMLData:(NSData *)data
{
   //returns an array with of all diamonds contained in the passed file
   NSString *xmlString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
   BOOL getDiamonds = YES;
   NSRange rangeToSearch = NSMakeRange(0, [xmlString length]);
   NSRange openRange, closeRange;
   NSMutableArray *diamondArray = [[[NSMutableArray alloc] init] autorelease];

   while(getDiamonds) {
      openRange = [xmlString rangeOfString:@"<diamond>" options:NSCaseInsensitiveSearch range:rangeToSearch];
      closeRange = [xmlString rangeOfString:@"</diamond>" options:NSCaseInsensitiveSearch range:rangeToSearch];

      if(openRange.location != NSNotFound && closeRange.location != NSNotFound) {
         //there is another diamond to add
         SSNeedsDiamond *newDiamond = [self diamondObjectFromXMLString:[xmlString substringWithRange:NSMakeRange(openRange.location,
                                                                                                                  ((closeRange.location + closeRange.length) - openRange.location))]];
         if(newDiamond) { //if not newDiamond there was a problem with the string we passed to diamondObjectFromXMLString: so we dont want to add it
            [diamondArray addObject:newDiamond];
         }
         rangeToSearch = NSMakeRange((closeRange.location + closeRange.length),
                                     ([xmlString length] - (closeRange.location + closeRange.length))); //move the start of rangeToSearch to the end of last </diamond> tag
      } else {
         //no more valid diamonds
         getDiamonds = NO;
      }
   }
   return diamondArray;
}

- (SSNeedsDiamond *)diamondObjectFromXMLString:(NSString *)xmlString
{
   SSNeedsDiamond *diamond;
   NSRange openRange = [xmlString rangeOfString:@"<diamond>" options:NSCaseInsensitiveSearch];
   NSRange closeRange = [xmlString rangeOfString:@"</diamond>" options:NSCaseInsensitiveSearch];
   NSString *ownerString;
   NSString *fileID;
   NSString *analyst;
   NSString *description;
   NSString *problemStateString;
   NSNumber *problemState;
   NSString *udValue1;
   NSString *udValue2;
   NSString *udValue3;
   NSString *udValue4;
   NSString *udValue5;
   NSString *udValue6;
   NSMutableArray *optionArray;

   if(openRange.location != NSNotFound && closeRange.location != NSNotFound) { //this test is just here in case the method is called without checking if there is an open and close tag
                                                                               //we should be dealing with a valid diamond
      ownerString = [self stringFromXMLString:xmlString openTag:@"<address>" closeTag:@"</address>"];
      fileID = [self stringFromXMLString:xmlString openTag:@"<file ID>" closeTag:@"</file ID>"];
      analyst = [self stringFromXMLString:xmlString openTag:@"<analyst>" closeTag:@"</analyst>"];
      problemStateString = [self stringFromXMLString:xmlString openTag:@"<problemState>" closeTag:@"</problemState>"];
      description = [self stringFromXMLString:xmlString openTag:@"<description>" closeTag:@"</description>"];
      udValue1 = [self stringFromXMLString:xmlString openTag:@"<udValue1>" closeTag:@"</udValue1>"];
      udValue2 = [self stringFromXMLString:xmlString openTag:@"<udValue2>" closeTag:@"</udValue2>"];
      udValue3 = [self stringFromXMLString:xmlString openTag:@"<udValue3>" closeTag:@"</udValue3>"];
      udValue4 = [self stringFromXMLString:xmlString openTag:@"<udValue4>" closeTag:@"</udValue4>"];
      udValue5 = [self stringFromXMLString:xmlString openTag:@"<udValue5>" closeTag:@"</udValue5>"];
      udValue6 = [self stringFromXMLString:xmlString openTag:@"<udValue6>" closeTag:@"</udValue6>"];
      optionArray = [self diamondOptionArrayFromXMLString:xmlString];


      if([problemStateString isEqualToString:@"done"] || [problemStateString isEqualToString:@"final"]) {
         problemState = [NSNumber numberWithInt:SSNeedsDiamondFinalState];
      } else {
         problemState = [NSNumber numberWithInt:SSNeedsDiamondDraftState];
      }

      diamond = [[[SSNeedsDiamond alloc] initWithOwner:ownerString
                                                      fileID:fileID
                                                     analyst:analyst
                                                 description:description
                                                    udValue1:udValue1
                                                    udValue2:udValue2
                                                    udValue3:udValue3
                                                    udValue4:udValue4
                                                    udValue5:udValue5
                                                    udValue6:udValue6
                                                 optionArray:optionArray
                                                diamondState:problemState] autorelease];

      return diamond;
   }
   
   return nil; //there was a problem making the diamond
}

- (NSMutableArray *)diamondOptionArrayFromXMLString:(NSString *)xmlString
{
   BOOL getOptions = YES;
   NSRange rangeToSearch = NSMakeRange(0, [xmlString length]);
   NSRange openRange, closeRange;
   NSMutableArray *optionArray = [[[NSMutableArray alloc] init] autorelease];
   while(getOptions) {
      openRange = [xmlString rangeOfString:@"<diamond_option>" options:NSCaseInsensitiveSearch range:rangeToSearch];
      closeRange = [xmlString rangeOfString:@"</diamond_option>" options:NSCaseInsensitiveSearch range:rangeToSearch];

      if(openRange.location != NSNotFound && closeRange.location != NSNotFound) {
         //there is another option to add
         SSDiamondOption *option = [self diamondOptionObjectFromString:[xmlString substringWithRange:NSMakeRange(openRange.location,
                                                                                                              ((closeRange.location + closeRange.length) - openRange.location))]];
         if(option) { //if not option there was a problem with the string we passed to diamondOptionObjectFromString: so we dont want to add it
            [optionArray addObject:option];
         }
         rangeToSearch = NSMakeRange((closeRange.location + closeRange.length),
                                     ([xmlString length] - (closeRange.location + closeRange.length))); //move the start of rangeToSearch to the end of last </diamond_option> tag
      } else {
         //no more valid option blocks
         getOptions = NO;
      }
   }
   return optionArray;
}

- (SSDiamondOption *)diamondOptionObjectFromString:(NSString *)xmlString
{
   SSDiamondOption *option;
   NSRange openRange = [xmlString rangeOfString:@"<diamond_option>" options:NSCaseInsensitiveSearch];
   NSRange closeRange = [xmlString rangeOfString:@"</diamond_option>" options:NSCaseInsensitiveSearch];
   NSString *optionValue;
   NSString *udValue1;
   NSString *udValue2;
   NSString *udValue3;
   NSString *udValue4;
   NSString *udValue5;
   NSMutableArray *tradeoffArray;

   if(openRange.location != NSNotFound && closeRange.location != NSNotFound) { //this test is just here in case the method is called without checking if there is an open and close tag
                                                                               //we should be dealing with a valid diamond option
      optionValue = [self stringFromXMLString:xmlString openTag:@"<option>" closeTag:@"</option>"];
      udValue1 = [self stringFromXMLString:xmlString openTag:@"<udValue1>" closeTag:@"</udValue1>"];
      udValue2 = [self stringFromXMLString:xmlString openTag:@"<udValue2>" closeTag:@"</udValue2>"];
      udValue3 = [self stringFromXMLString:xmlString openTag:@"<udValue3>" closeTag:@"</udValue3>"];
      udValue4 = [self stringFromXMLString:xmlString openTag:@"<udValue4>" closeTag:@"</udValue4>"];
      udValue5 = [self stringFromXMLString:xmlString openTag:@"<udValue5>" closeTag:@"</udValue5>"];
      tradeoffArray = [self diamondTradeoffArrayFromXMLString:xmlString];

      option = [[[SSDiamondOption alloc] initWithTradeoffArray:tradeoffArray
                                                        option:optionValue
                                                      udValue1:udValue1
                                                      udValue2:udValue2
                                                      udValue3:udValue3
                                                      udValue4:udValue4
                                                      udValue5:udValue5] autorelease];

      return option;
   }

   return nil; //there was a problem making the diamond
}

- (NSMutableArray *)diamondTradeoffArrayFromXMLString:(NSString *)xmlString
{
   BOOL getTradeoffs = YES;
   NSRange rangeToSearch = NSMakeRange(0, [xmlString length]);
   NSRange openRange, closeRange;
   NSMutableArray *tradeoffArray = [[[NSMutableArray alloc] init] autorelease];
   while(getTradeoffs) {
      openRange = [xmlString rangeOfString:@"<tradeoff>" options:NSCaseInsensitiveSearch range:rangeToSearch];
      closeRange = [xmlString rangeOfString:@"</tradeoff>" options:NSCaseInsensitiveSearch range:rangeToSearch];

      if(openRange.location != NSNotFound && closeRange.location != NSNotFound) {
         //there is another tradeoff to add
         NSString *pro = [self stringFromXMLString:[xmlString substringWithRange:NSMakeRange(openRange.location,
                                                                                             ((closeRange.location + closeRange.length) - openRange.location))]
                                           openTag:@"<pro>"
                                          closeTag:@"</pro>"];

         NSString *con = [self stringFromXMLString:[xmlString substringWithRange:NSMakeRange(openRange.location,
                                                                                             ((closeRange.location + closeRange.length) - openRange.location))]
                                           openTag:@"<con>"
                                          closeTag:@"</con>"];
         NSString *udValue1 = [self stringFromXMLString:[xmlString substringWithRange:NSMakeRange(openRange.location,
                                                                                                  ((closeRange.location + closeRange.length) - openRange.location))]
                                                openTag:@"<udValue1>"
                                               closeTag:@"</udValue1>"];
         NSString *udValue2 = [self stringFromXMLString:[xmlString substringWithRange:NSMakeRange(openRange.location,
                                                                                                  ((closeRange.location + closeRange.length) - openRange.location))]
                                                openTag:@"<udValue2>"
                                               closeTag:@"</udValue2>"];
         NSString *udValue3 = [self stringFromXMLString:[xmlString substringWithRange:NSMakeRange(openRange.location,
                                                                                                  ((closeRange.location + closeRange.length) - openRange.location))]
                                                openTag:@"<udValue3>"
                                               closeTag:@"</udValue3>"];


         SSDiamondTradeoff *tradeoff = [[SSDiamondTradeoff alloc] initWithPro:pro
                                                                          con:con
                                                                     udValue1:udValue1
                                                                     udValue2:udValue2
                                                                     udValue3:udValue3];

         [tradeoffArray addObject:tradeoff];

         rangeToSearch = NSMakeRange((closeRange.location + closeRange.length),
                                     ([xmlString length] - (closeRange.location + closeRange.length))); //move the start of rangeToSearch to the end of last </diamond_option> tag
      } else {
         //no more valid option blocks
         getTradeoffs = NO;
      }
   }
   return tradeoffArray;
}
- (NSString *)stringFromXMLString:(NSString *)xmlString openTag:(NSString *)openTag closeTag:(NSString *)closeTag
{
   //stringFromXMLString pulls a the string contained between the first instances of openTag and closeTag.
   //if openTag or closeTag is not present returns empty string
   
   NSRange openRange = [xmlString rangeOfString:openTag options:NSCaseInsensitiveSearch];
   NSRange closeRange;

   if(openRange.location != NSNotFound) {
      closeRange = [xmlString rangeOfString:closeTag options:NSCaseInsensitiveSearch range:NSMakeRange((openRange.location + openRange.length),
                                                                                                       [xmlString length] - (openRange.location + openRange.length))];
   }

   if(openRange.location != NSNotFound && openRange.location != NSNotFound)  {
      //we have the open and close tags
      int location = (openRange.location + openRange.length);
      int length = (closeRange.location - location);

      return [xmlString substringWithRange:NSMakeRange(location, length)];
   }

   return [NSString string]; //either the open or close tag wasn't present so we can't get the string
}

- (NSMutableArray *)commentArrayFromXMLString:(NSString *)xmlString
{
   //commentArrayFromXMLString builds and returns an array containing all of the valid comments within xmlString
   
   BOOL getComments = YES;
   NSRange rangeToSearch = NSMakeRange(0, [xmlString length]);
   NSRange openRange, closeRange;
   NSMutableArray *commentArray = [[[NSMutableArray alloc] init] autorelease];
   while(getComments) {
      openRange = [xmlString rangeOfString:@"<comment unique_id=\"" options:NSCaseInsensitiveSearch range:rangeToSearch];
      closeRange = [xmlString rangeOfString:@"</comment>" options:NSCaseInsensitiveSearch range:rangeToSearch];

      if(openRange.location != NSNotFound && closeRange.location != NSNotFound) {
         //there is another comment to add
         SSCatalystComment *comment = [self commentObjectFromString:[xmlString substringWithRange:NSMakeRange(openRange.location,
                                                                                                         ((closeRange.location + closeRange.length) - openRange.location))]];
         if(comment) { //if not comment there was a problem with the string we passed to commentObjectFromString: so we dont want to add it
            [commentArray addObject:comment];
         }
         rangeToSearch = NSMakeRange((closeRange.location + closeRange.length),
                                     ([xmlString length] - (closeRange.location + closeRange.length))); //move the start of rangeToSearch to the end of last </comment> tag
      } else {
         //no more valid comment blocks
         getComments = NO;
      }
   }
   return commentArray;
}

- (SSCatalystComment *)commentObjectFromString:(NSString *)commentString
{
   //commentObjectFromString builds and returns a SSCatalystComment from the first set of comments present in commentString
   //if no valid comment blocks are present returns nil

   SSCatalystComment *commentObject;
   NSRange openRange = [commentString rangeOfString:@"<comment unique_id=\"" options:NSCaseInsensitiveSearch];
   NSRange closeRange = [commentString rangeOfString:@"</comment>" options:NSCaseInsensitiveSearch];
   NSString *idString, *comment, *typeString, *ceID;
   NSNumber *type;
   NSPoint position, ceOffset;
   if(openRange.location != NSNotFound && closeRange.location != NSNotFound) { //this test is just here in case the method is called without checking if there is an open and close tag
    //we should be dealing with a valid comment block
      //get the unique_id
      idString = [self stringFromXMLString:commentString openTag:@"unique_id=\"" closeTag:@"\""];
      typeString = [self stringFromXMLString:commentString openTag:@"type=\"" closeTag:@"\""];
      comment = [self stringFromXMLString:commentString openTag:@"<text>" closeTag:@"</text>"];
      position = NSMakePoint([[self xPositionFromString:commentString] floatValue], [[self yPositionFromString:commentString] floatValue]);
      ceID = [self stringFromXMLString:commentString openTag:@"<closest_event unique_id=\"" closeTag:@"\">"];
      ceOffset = NSMakePoint([[self closestEventXOffsetFromString:commentString] floatValue], [[self closestEventYOffsetFromString:commentString] floatValue]);
      

      if([typeString isEqualToString:@"question"]) {
         type = [NSNumber numberWithInt:SSCommentQuestionType];
      } else {
         if([typeString isEqualToString:@"note"]) {
            type = [NSNumber numberWithInt:SSCommentNoteType];
         } else {
            type = [NSNumber numberWithInt:SSCommentAlertType];
         }
      }
      

      commentObject = [[[SSCatalystComment alloc] initWithUnique_id:idString comment:comment type:type position:position closestEvent:ceID closestEventOffset:ceOffset] autorelease];
      return commentObject;
   }
   return nil; //there was a problem making the commentObject
}

- (NSNumber *)labelFromString:(NSString *)string
{
   NSString *labelString = [self stringFromXMLString:string openTag:[NSString stringWithFormat:@"<label>"] closeTag:@"</label>"];
   NSNumber *label;

   //get the corresponding number for the word used in structure file
   if([labelString isEqualToString:@"yellow"]) {
      label = [NSNumber numberWithInt:SSYellowLabelType];
   } else if([labelString isEqualToString:@"green"]) {
      label = [NSNumber numberWithInt:SSGreenLabelType];
   } else if([labelString isEqualToString:@"light blue"]) {
      label = [NSNumber numberWithInt:SSLightBlueLabelType];
   } else if([labelString isEqualToString:@"blue"]) {
      label = [NSNumber numberWithInt:SSBlueLabelType];
   } else if([labelString isEqualToString:@"purple"]) {
      label = [NSNumber numberWithInt:SSPurpleLabelType];
   } else if([labelString isEqualToString:@"orange"]) {
      label = [NSNumber numberWithInt:SSOrangeLabelType];
   } else if([labelString isEqualToString:@"red"]) {
      label = [NSNumber numberWithInt:SSRedLabelType];
   } else {
      label = [NSNumber numberWithInt:SSWhiteLabelType];
   }

   return label;
}

- (NSNumber *)xPositionFromString:(NSString *)string
{
   //xPositionFromString returns an NSNumber containing the number enclosed within the first x unit block in string
   //returns 0 if no valid x unit block is present
   
   NSRange openRange = [string rangeOfString:@"<x unit=\"" options:NSCaseInsensitiveSearch];
   NSRange closeRange = [string rangeOfString:@"</x>" options:NSCaseInsensitiveSearch];
   NSRange numStringStartRange;
   NSScanner *intScanner;
   int xPos;
   NSString *numberString;

   if(openRange.location != NSNotFound && closeRange.location != NSNotFound) {
      //it looks valid so lets try and pull the number
      numStringStartRange = [string rangeOfString:@"\">" options:NSCaseInsensitiveSearch range:NSMakeRange(openRange.location,
                                                                                                           (closeRange.location - openRange.location))];
      numberString = [string substringWithRange:NSMakeRange((numStringStartRange.location + numStringStartRange.length),
                                                            (closeRange.location - (numStringStartRange.location + numStringStartRange.length)))];
      intScanner = [NSScanner scannerWithString:numberString];
      if([intScanner scanInt:&xPos]) {
         //we successfully scanned an int
         return [NSNumber numberWithInt:xPos];
      }
   }
   return [NSNumber numberWithInt:0];; //the passed string wasn't valid
}

- (NSNumber *)yPositionFromString:(NSString *)string
{
   //yPositionFromString returns an NSNumber containing the number enclosed within the first y unit block in string
   //returns 0 if no valid y unit block is present
   
   NSRange openRange = [string rangeOfString:@"<y unit=\"" options:NSCaseInsensitiveSearch];
   NSRange closeRange = [string rangeOfString:@"</y>" options:NSCaseInsensitiveSearch];
   NSRange numStringStartRange;
   NSScanner *intScanner;
   int yPos;
   NSString *numberString;

   if(openRange.location != NSNotFound && closeRange.location != NSNotFound) {
      //it looks valid so lets try and pull the number
      numStringStartRange = [string rangeOfString:@"\">" options:NSCaseInsensitiveSearch range:NSMakeRange(openRange.location,
                                                                                                           (closeRange.location - openRange.location))];
      numberString = [string substringWithRange:NSMakeRange((numStringStartRange.location + numStringStartRange.length),
                                                            (closeRange.location - (numStringStartRange.location + numStringStartRange.length)))];
      intScanner = [NSScanner scannerWithString:numberString];
      if([intScanner scanInt:&yPos]) {
         //we successfully scanned an int
         return [NSNumber numberWithInt:yPos];
      }
   }
   return [NSNumber numberWithInt:0]; //the passed string wasn't valid
}

- (NSNumber *)closestEventXOffsetFromString:(NSString *)string
{
   NSRange openRange = [string rangeOfString:@"<closest_event x_offset>" options:NSCaseInsensitiveSearch];
   NSRange closeRange = [string rangeOfString:@"</closest_event x_offset>" options:NSCaseInsensitiveSearch];
   NSRange numStringStartRange;
   NSScanner *intScanner;
   int xPos;
   NSString *numberString;

   if(openRange.location != NSNotFound && closeRange.location != NSNotFound) {
      //it looks valid so lets try and pull the number
      numStringStartRange = [string rangeOfString:@"<closest_event x_offset>" options:NSCaseInsensitiveSearch range:NSMakeRange(openRange.location,
                                                                                                           (closeRange.location - openRange.location))];
      numberString = [string substringWithRange:NSMakeRange((numStringStartRange.location + numStringStartRange.length),
                                                            (closeRange.location - (numStringStartRange.location + numStringStartRange.length)))];
      intScanner = [NSScanner scannerWithString:numberString];
      if([intScanner scanInt:&xPos]) {
         //we successfully scanned an int
         return [NSNumber numberWithInt:xPos];
      }
   }
   return [NSNumber numberWithInt:0]; //the passed string wasn't valid
}

- (NSNumber *)closestEventYOffsetFromString:(NSString *)string
{
   NSRange openRange = [string rangeOfString:@"<closest_event y_offset>" options:NSCaseInsensitiveSearch];
   NSRange closeRange = [string rangeOfString:@"</closest_event y_offset>" options:NSCaseInsensitiveSearch];
   NSRange numStringStartRange;
   NSScanner *intScanner;
   int yPos;
   NSString *numberString;

   if(openRange.location != NSNotFound && closeRange.location != NSNotFound) {
      //it looks valid so lets try and pull the number
      numStringStartRange = [string rangeOfString:@"<closest_event y_offset>" options:NSCaseInsensitiveSearch range:NSMakeRange(openRange.location,
                                                                                                           (closeRange.location - openRange.location))];
      numberString = [string substringWithRange:NSMakeRange((numStringStartRange.location + numStringStartRange.length),
                                                            (closeRange.location - (numStringStartRange.location + numStringStartRange.length)))];
      intScanner = [NSScanner scannerWithString:numberString];
      if([intScanner scanInt:&yPos]) {
         //we successfully scanned an int
         return [NSNumber numberWithInt:yPos];
      }
   }
   return [NSNumber numberWithInt:0]; //the passed string wasn't valid
}

- (SSChartDate *)ssChartDateFromXMLString:(NSString *)xmlString openTag:(NSString *)openTag closeTag:(NSString *)closeTag
{   
   NSString *typeString = [self stringFromXMLString:xmlString openTag:openTag closeTag:@"\">"];
   NSString *stringForDates = [self stringFromXMLString:xmlString openTag:openTag closeTag:closeTag];
   NSString *year = [self stringFromXMLString:stringForDates openTag:@"<year>" closeTag:@"</year>"];
   NSString *month = [self stringFromXMLString:stringForDates openTag:@"<month>" closeTag:@"</month>"];
   NSString *day = [self stringFromXMLString:stringForDates openTag:@"<day>" closeTag:@"</day>"];
   NSString *hour = [self stringFromXMLString:stringForDates openTag:@"<hour>" closeTag:@"</hour>"];
   NSString *minute = [self stringFromXMLString:stringForDates openTag:@"<minute>" closeTag:@"</minute>"];
   NSString *second = [self stringFromXMLString:stringForDates openTag:@"<second>" closeTag:@"</second>"];
   NSString *millisecond = [self stringFromXMLString:stringForDates openTag:@"<millisecond>" closeTag:@"</millisecond>"];
   SSChartDate *time;
   BOOL isEstimated;

   if([typeString isEqualToString:@"observed"]) {
      isEstimated = NO;
   } else {
      isEstimated = YES;
   }

   if(![year isEqualToString:[NSString string]] && ![month isEqualToString:[NSString string]] && ![day isEqualToString:[NSString string]]
      && ![hour isEqualToString:[NSString string]] && ![minute isEqualToString:[NSString string]] && ![second isEqualToString:[NSString string]]
      && ![millisecond isEqualToString:[NSString string]]) {
      time = [[[SSChartDate alloc] initWithString:[NSString stringWithFormat:@"%@/%@/%@/%@/%@/%@/%@", year, month, day, hour, minute, second, millisecond]
                                  calendarFormat:@"%Y/%m/%d/%H/%M/%S/%F" isEstimated:isEstimated] autorelease];
   } else {
      //time wasnt valid so set it to now
      time = [SSChartDate calendarDate];
   }

   return time;
}

- (NSCalendarDate *)chartDateFromXMLString:(NSString *)xmlString
{
   //this method makes a NSCalendarDate out of the structure files <chart_Date> information
   NSString *year = [self stringFromXMLString:xmlString openTag:@"<year>" closeTag:@"</year>"];
   NSString *month = [self stringFromXMLString:xmlString openTag:@"<month>" closeTag:@"</month>"];
   NSString *day = [self stringFromXMLString:xmlString openTag:@"<day>" closeTag:@"</day>"];
   NSCalendarDate *date;

   if(![year isEqualToString:[NSString string]] && ![month isEqualToString:[NSString string]] && ![day isEqualToString:[NSString string]]) {
      date = [[[NSCalendarDate alloc] initWithString:[NSString stringWithFormat:@"%@/%@/%@", year, month, day]
                                   calendarFormat:@"%Y/%m/%d"] autorelease];
   } else {
      //time wasn't valid so set it to now
      date = [NSCalendarDate calendarDate];
   }

   return date;
}

- (NSMutableArray *)linkArrayFromXMLString:(NSString *)xmlString
{
   //linkArrayFromXMLString returns an array containing the links within the first link block encounted
   //returns empty array is no links present
   
   BOOL getLinks = YES;
   NSRange rangeToSearch = NSMakeRange(0, [xmlString length]);
   NSRange openRange, closeRange;
   NSMutableArray *linkArray = [[[NSMutableArray alloc] init] autorelease];

   while(getLinks) {
      openRange = [xmlString rangeOfString:@"<link type=\"" options:NSCaseInsensitiveSearch range:rangeToSearch];
      closeRange = [xmlString rangeOfString:@"</link>" options:NSCaseInsensitiveSearch range:rangeToSearch];

      if(openRange.location != NSNotFound && closeRange.location != NSNotFound) {
         //there is another link to add
         NSMutableDictionary *dictionary = [self linkDictionaryFromString:[xmlString substringWithRange:NSMakeRange(openRange.location,
                                                                                                                       ((closeRange.location + closeRange.length) - openRange.location))]];
         if(dictionary) { //if not dictionary there was a problem with the string we passed to linkDictionaryFromString: so we dont want to add it
            [linkArray addObject:dictionary];
         }
         rangeToSearch = NSMakeRange((closeRange.location + closeRange.length),
                                     ([xmlString length] - (closeRange.location + closeRange.length))); //move the start of rangeToSearch to the end of last </comment> tag
      } else {
         //no more valid link blocks
         getLinks = NO;
      }
   }
   return linkArray;
}

- (NSMutableDictionary *)linkDictionaryFromString:(NSString *)linkString
{
   //linkDictionaryFromString builds and returns an NSMutableDictionary containing the first link and class found
   //returns nil if no valid link tags
   
   NSMutableDictionary *linkDictionary;
   NSRange openRange = [linkString rangeOfString:@"<link type=\"" options:NSCaseInsensitiveSearch];
   NSRange closeRange = [linkString rangeOfString:@"</link>" options:NSCaseInsensitiveSearch];
   NSString *typeString, *link;
   NSNumber *type;

   if(openRange.location != NSNotFound && closeRange.location != NSNotFound) { //this test is just here in case the method is called without checking if there is an open and close tag
                                                                               //we should be dealing with a valid link block
                                                                               //get the class
      typeString = [self stringFromXMLString:linkString openTag:@"<link type=\"" closeTag:@"\">"];
      link = [self stringFromXMLString:linkString openTag:[NSString stringWithFormat:@"<link type=\"%@\">", typeString] closeTag:@"</link>"];

      //we successfully got all of the values so lets build the linkDictionary
      if([typeString isEqualToString:@"confirmed"]) {
         type = [NSNumber numberWithInt:SSConfirmedLinkType];
      } else {
         //set it to Tentative
         type = [NSNumber numberWithInt:SSTentativeLinkType];
      }
         
      linkDictionary = [[[NSMutableDictionary alloc] init] autorelease];
      [linkDictionary setObject:link forKey:@"link"];
      [linkDictionary setObject:type forKey:@"type"];
      return linkDictionary;
   }
   return nil; //there was a problem making the dictionary
}

- (float)actorColumnWidthFromXMLString:(NSString *)xmlString
{
   NSString *numString = [self stringFromXMLString:xmlString openTag:@"<actor_column_width>" closeTag:@"</actor_column_width>"];
   return [numString floatValue];
}

- (int)displayModeFromXMLString:(NSString *)xmlString
{
   NSString *numString = [self stringFromXMLString:xmlString openTag:@"<display_mode>" closeTag:@"</display_mode>"];
   return [numString intValue];
}

- (float)overlapUnitFromXMLString:(NSString *)xmlString
{
   NSString *numString = [self stringFromXMLString:xmlString openTag:@"<overlap_unit>" closeTag:@"</overlap_unit>"];
   return [numString floatValue];
}

- (float)overlapPixelsPerUnitFromXMLString:(NSString *)xmlString
{
   NSString *numString = [self stringFromXMLString:xmlString openTag:@"<overlap_pixels_per_unit>" closeTag:@"</overlap_pixels_per_unit>"];
   return [numString floatValue];
}

- (NSMutableArray *)poisonWordArrayFromXMLString:(NSString *)xmlString
{
   //poisonWordArrayFromXMLString builds and returns an array containing all of the valid poison words within xmlString

   BOOL getPoisonWords = YES;
   NSRange rangeToSearch = NSMakeRange(0, [xmlString length]);
   NSRange openRange, closeRange;
   NSMutableArray *poisonWordArray = [[[NSMutableArray alloc] init] autorelease];
   openRange = [xmlString rangeOfString:@"<poison_word_list>" options:NSCaseInsensitiveSearch range:rangeToSearch];

   if(openRange.location == NSNotFound) {
      //there is no poison word tag so this file must be old add the 2 defult words
      [poisonWordArray addObject:[[[SSPoisonWord alloc] initWithPoisonWord:NSLocalizedString(@"Was", nil)
                                                            warningMessage:NSLocalizedString(@"WasActionWarning", nil)] autorelease]];
      [poisonWordArray addObject:[[[SSPoisonWord alloc] initWithPoisonWord:NSLocalizedString(@"Not", nil)
                                                            warningMessage:NSLocalizedString(@"NotActionWarning", nil)] autorelease]];
      return poisonWordArray;
   }
   
   while(getPoisonWords) {
      openRange = [xmlString rangeOfString:@"<poison_word>" options:NSCaseInsensitiveSearch range:rangeToSearch];
      closeRange = [xmlString rangeOfString:@"</poison_word>" options:NSCaseInsensitiveSearch range:rangeToSearch];

      if(openRange.location != NSNotFound && closeRange.location != NSNotFound) {
         //there is another poison word to add
         SSPoisonWord *poisonWord = [self poisonWordObjectFromString:[xmlString substringWithRange:NSMakeRange(openRange.location,
                                                                                                               ((closeRange.location + closeRange.length) - openRange.location))]];
         if(poisonWord) { //if not comment there was a problem with the string we passed to commentObjectFromString: so we dont want to add it
            [poisonWordArray addObject:poisonWord];
         }
         rangeToSearch = NSMakeRange((closeRange.location + closeRange.length),
                                     ([xmlString length] - (closeRange.location + closeRange.length))); //move the start of rangeToSearch to the end of last </poison_word> tag
      } else {
         //no more valid poison words
         getPoisonWords = NO;
      }
   }
   return poisonWordArray;
   
}

- (SSPoisonWord *)poisonWordObjectFromString:(NSString *)poisonWordString
{
   //poisonWordObjectFromString builds and returns a SSPoisonWord from the first set of poision word tags present in poisonWordString
   //if no valid poison words are present returns nil

   SSPoisonWord *poisonWordObject;
   NSRange openRange = [poisonWordString rangeOfString:@"<poison_word>" options:NSCaseInsensitiveSearch];
   NSRange closeRange = [poisonWordString rangeOfString:@"</poison_word>" options:NSCaseInsensitiveSearch];
   NSString *word, *warning;

   if(openRange.location != NSNotFound && closeRange.location != NSNotFound) { //this test is just here in case the method is called without checking if there is an open and close tag
                                                                               //we should be dealing with a valid poisonword

      word = [self stringFromXMLString:poisonWordString openTag:@"<word>" closeTag:@"</word>"];
      warning = [self stringFromXMLString:poisonWordString openTag:@"<warning_message>" closeTag:@"</warning_message>"];


      poisonWordObject = [[[SSPoisonWord alloc] initWithPoisonWord:word
                                                    warningMessage:warning] autorelease];
      return poisonWordObject;
   }

   return nil; //there was a problem making the poison word
}



int sortActorOrderArray(NSMutableDictionary *obj1, NSMutableDictionary *obj2, void *context )
{
   //sorts actor order array, should only be passed dictionaries which have an NSNumber for the key row

   if([obj1 objectForKey:@"row"] && [obj2 objectForKey:@"row"]) {
      return [(NSNumber *)[obj1 objectForKey:@"row"] compare:(NSNumber *)[obj2 objectForKey:@"row"]];
   } else {
      return NSOrderedSame;
   }
}

@end